#include "../../include/json_cpp/writer.h"
#include <utility>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <iomanip>

#if _MSC_VER >= 1400 // VC++ 8.0
#pragma warning( disable : 4996 )   // disable warning about strdup being deprecated.
#endif

namespace Json
{

static bool isControlCharacter(char ch)
{
	return ch > 0 && ch <= 0x1F;
}

static bool containsControlCharacter(const char* str)
{
	while (*str)
	{
		if (isControlCharacter(*(str++)))
			return true;
	}
	return false;
}
static void uintToString(unsigned int value, char *&current)
{
	*--current = 0;
	do
	{
		*--current = (value % 10) + '0';
		value /= 10;
	} while (value != 0);
}

std::string valueToString(Value::Int value)
{
	char buffer[32];
	char *current = buffer + sizeof(buffer);
	bool isNegative = value < 0;
	if (isNegative)
		value = -value;
	uintToString(Value::UInt(value), current);
	if (isNegative)
		*--current = '-';
	assert( current >= buffer );
	return current;
}

std::string valueToString(Value::UInt value)
{
	char buffer[32];
	char *current = buffer + sizeof(buffer);
	uintToString(value, current);
	assert( current >= buffer );
	return current;
}

std::string valueToString(double value)
{
	char buffer[32];
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. 
	sprintf_s(buffer, sizeof(buffer), "%#.16g", value);
#else	
	sprintf(buffer, "%#.16g", value);
#endif
	char* ch = buffer + strlen(buffer) - 1;
	if (*ch != '0')
		return buffer; // nothing to truncate, so save time
	while (ch > buffer && *ch == '0')
	{
		--ch;
	}
	char* last_nonzero = ch;
	while (ch >= buffer)
	{
		switch (*ch)
		{
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			--ch;
			continue;
		case '.':
			// Truncate zeroes to save bytes in output, but keep one.
			*(last_nonzero + 2) = '\0';
			return buffer;
		default:
			return buffer;
		}
	}
	return buffer;
}

std::string valueToString(bool value)
{
	return value ? "true" : "false";
}

std::string valueToQuotedString(const char *value)
{
	// Not sure how to handle unicode...
	if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter(
			value))
		return std::string("\"") + value + "\"";
	// We have to walk value and escape any special characters.
	// Appending to std::string is not efficient, but this should be rare.
	// (Note: forward slashes are *not* rare, but I am not escaping them.)
	unsigned maxsize = strlen(value) * 2 + 3; // allescaped+quotes+NULL
	std::string result;
	result.reserve(maxsize); // to avoid lots of mallocs
	result += "\"";
	for (const char* c = value; *c != 0; ++c)
	{
		switch (*c)
		{
		case '\"':
			result += "\\\"";
			break;
		case '\\':
			result += "\\\\";
			break;
		case '\b':
			result += "\\b";
			break;
		case '\f':
			result += "\\f";
			break;
		case '\n':
			result += "\\n";
			break;
		case '\r':
			result += "\\r";
			break;
		case '\t':
			result += "\\t";
			break;
			//case '/':
			// Even though \/ is considered a legal escape in JSON, a bare
			// slash is also legal, so I see no reason to escape it.
			// (I hope I am not misunderstanding something.
			// blep notes: actually escaping \/ may be useful in javascript to avoid </
			// sequence.
			// Should add a flag to allow this compatibility mode and prevent this
			// sequence from occurring.
		default:
			if (isControlCharacter(*c))
			{
				std::ostringstream oss;
				oss << "\\u" << std::hex << std::uppercase << std::setfill('0')
						<< std::setw(4) << static_cast<int> (*c);
				result += oss.str();
			}
			else
			{
				result += *c;
			}
			break;
		}
	}
	result += "\"";
	return result;
}

// Class Writer
// //////////////////////////////////////////////////////////////////
Writer::~Writer()
{
}

// Class FastWriter
// //////////////////////////////////////////////////////////////////

FastWriter::FastWriter() :
	yamlCompatiblityEnabled_(false)
{
}

void FastWriter::enableYAMLCompatibility()
{
	yamlCompatiblityEnabled_ = true;
}

std::string FastWriter::write(const Value &root)
{
	document_ = "";
	writeValue(root);
	document_ += "\n";
	return document_;
}

void FastWriter::writeValue(const Value &value)
{
	switch (value.type())
	{
	case nullValue:
		document_ += "null";
		break;
	case intValue:
		document_ += valueToString(value.asInt());
		break;
	case uintValue:
		document_ += valueToString(value.asUInt());
		break;
	case realValue:
		document_ += valueToString(value.asDouble());
		break;
	case stringValue:
		document_ += valueToQuotedString(value.asCString());
		break;
	case booleanValue:
		document_ += valueToString(value.asBool());
		break;
	case arrayValue:
	{
		document_ += "[";
		int size = value.size();
		for (int index = 0; index < size; ++index)
		{
			if (index > 0)
				document_ += ",";
			writeValue(value[index]);
		}
		document_ += "]";
	}
		break;
	case objectValue:
	{
		Value::Members members(value.getMemberNames());
		document_ += "{";
		for (Value::Members::iterator it = members.begin(); it != members.end(); ++it)
		{
			const std::string &name = *it;
			if (it != members.begin())
				document_ += ",";
			document_ += valueToQuotedString(name.c_str());
			document_ += yamlCompatiblityEnabled_ ? ": " : ":";
			writeValue(value[name]);
		}
		document_ += "}";
	}
		break;
	}
}

// Class StyledWriter
// //////////////////////////////////////////////////////////////////

StyledWriter::StyledWriter() :
	rightMargin_(74), indentSize_(3)
{
}

std::string StyledWriter::write(const Value &root)
{
	document_ = "";
	addChildValues_ = false;
	indentString_ = "";
	writeCommentBeforeValue(root);
	writeValue(root);
	writeCommentAfterValueOnSameLine(root);
	document_ += "\n";
	return document_;
}

void StyledWriter::writeValue(const Value &value)
{
	switch (value.type())
	{
	case nullValue:
		pushValue("null");
		break;
	case intValue:
		pushValue(valueToString(value.asInt()));
		break;
	case uintValue:
		pushValue(valueToString(value.asUInt()));
		break;
	case realValue:
		pushValue(valueToString(value.asDouble()));
		break;
	case stringValue:
		pushValue(valueToQuotedString(value.asCString()));
		break;
	case booleanValue:
		pushValue(valueToString(value.asBool()));
		break;
	case arrayValue:
		writeArrayValue(value);
		break;
	case objectValue:
	{
		Value::Members members(value.getMemberNames());
		if (members.empty())
			pushValue("{}");
		else
		{
			writeWithIndent("{");
			indent();
			Value::Members::iterator it = members.begin();
			while (true)
			{
				const std::string &name = *it;
				const Value &childValue = value[name];
				writeCommentBeforeValue(childValue);
				writeWithIndent(valueToQuotedString(name.c_str()));
				document_ += " : ";
				writeValue(childValue);
				if (++it == members.end())
				{
					writeCommentAfterValueOnSameLine(childValue);
					break;
				}
				document_ += ",";
				writeCommentAfterValueOnSameLine(childValue);
			}
			unindent();
			writeWithIndent("}");
		}
	}
		break;
	}
}

void StyledWriter::writeArrayValue(const Value &value)
{
	unsigned size = value.size();
	if (size == 0)
		pushValue("[]");
	else
	{
		bool isArrayMultiLine = isMultineArray(value);
		if (isArrayMultiLine)
		{
			writeWithIndent("[");
			indent();
			bool hasChildValue = !childValues_.empty();
			unsigned index = 0;
			while (true)
			{
				const Value &childValue = value[index];
				writeCommentBeforeValue(childValue);
				if (hasChildValue)
					writeWithIndent(childValues_[index]);
				else
				{
					writeIndent();
					writeValue(childValue);
				}
				if (++index == size)
				{
					writeCommentAfterValueOnSameLine(childValue);
					break;
				}
				document_ += ",";
				writeCommentAfterValueOnSameLine(childValue);
			}
			unindent();
			writeWithIndent("]");
		}
		else // output on a single line
		{
			assert( childValues_.size() == size );
			document_ += "[ ";
			for (unsigned index = 0; index < size; ++index)
			{
				if (index > 0)
					document_ += ", ";
				document_ += childValues_[index];
			}
			document_ += " ]";
		}
	}
}

bool StyledWriter::isMultineArray(const Value &value)
{
	int size = value.size();
	bool isMultiLine = size * 3 >= rightMargin_;
	childValues_.clear();
	for (int index = 0; index < size && !isMultiLine; ++index)
	{
		const Value &childValue = value[index];
		isMultiLine = isMultiLine || ((childValue.isArray()
				|| childValue.isObject()) && childValue.size() > 0);
	}
	if (!isMultiLine) // check if line length > max line length
	{
		childValues_.reserve(size);
		addChildValues_ = true;
		int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
		for (int index = 0; index < size && !isMultiLine; ++index)
		{
			writeValue(value[index]);
			lineLength += int(childValues_[index].length());
			isMultiLine = isMultiLine && hasCommentForValue(value[index]);
		}
		addChildValues_ = false;
		isMultiLine = isMultiLine || lineLength >= rightMargin_;
	}
	return isMultiLine;
}

void StyledWriter::pushValue(const std::string &value)
{
	if (addChildValues_)
		childValues_.push_back(value);
	else
		document_ += value;
}

void StyledWriter::writeIndent()
{
	if (!document_.empty())
	{
		char last = document_[document_.length() - 1];
		if (last == ' ') // already indented
			return;
		if (last != '\n') // Comments may add new-line
			document_ += '\n';
	}
	document_ += indentString_;
}

void StyledWriter::writeWithIndent(const std::string &value)
{
	writeIndent();
	document_ += value;
}

void StyledWriter::indent()
{
	indentString_ += std::string(indentSize_, ' ');
}

void StyledWriter::unindent()
{
	assert( int(indentString_.size()) >= indentSize_ );
	indentString_.resize(indentString_.size() - indentSize_);
}

void StyledWriter::writeCommentBeforeValue(const Value &root)
{
	if (!root.hasComment(commentBefore))
		return;
	document_ += normalizeEOL(root.getComment(commentBefore));
	document_ += "\n";
}

void StyledWriter::writeCommentAfterValueOnSameLine(const Value &root)
{
	if (root.hasComment(commentAfterOnSameLine))
		document_ += " "
				+ normalizeEOL(root.getComment(commentAfterOnSameLine));

	if (root.hasComment(commentAfter))
	{
		document_ += "\n";
		document_ += normalizeEOL(root.getComment(commentAfter));
		document_ += "\n";
	}
}

bool StyledWriter::hasCommentForValue(const Value &value)
{
	return value.hasComment(commentBefore) || value.hasComment(
			commentAfterOnSameLine) || value.hasComment(commentAfter);
}

std::string StyledWriter::normalizeEOL(const std::string &text)
{
	std::string normalized;
	normalized.reserve(text.length());
	const char *begin = text.c_str();
	const char *end = begin + text.length();
	const char *current = begin;
	while (current != end)
	{
		char c = *current++;
		if (c == '\r') // mac or dos EOL
		{
			if (*current == '\n') // convert dos EOL
				++current;
			normalized += '\n';
		}
		else
			// handle unix EOL & other char
			normalized += c;
	}
	return normalized;
}

// Class StyledStreamWriter
// //////////////////////////////////////////////////////////////////

StyledStreamWriter::StyledStreamWriter(std::string indentation) :
	document_(NULL), rightMargin_(74), indentation_(indentation)
{
}

void StyledStreamWriter::write(std::ostream &out, const Value &root)
{
	document_ = &out;
	addChildValues_ = false;
	indentString_ = "";
	writeCommentBeforeValue(root);
	writeValue(root);
	writeCommentAfterValueOnSameLine(root);
	*document_ << "\n";
	document_ = NULL; // Forget the stream, for safety.
}

void StyledStreamWriter::writeValue(const Value &value)
{
	switch (value.type())
	{
	case nullValue:
		pushValue("null");
		break;
	case intValue:
		pushValue(valueToString(value.asInt()));
		break;
	case uintValue:
		pushValue(valueToString(value.asUInt()));
		break;
	case realValue:
		pushValue(valueToString(value.asDouble()));
		break;
	case stringValue:
		pushValue(valueToQuotedString(value.asCString()));
		break;
	case booleanValue:
		pushValue(valueToString(value.asBool()));
		break;
	case arrayValue:
		writeArrayValue(value);
		break;
	case objectValue:
	{
		Value::Members members(value.getMemberNames());
		if (members.empty())
			pushValue("{}");
		else
		{
			writeWithIndent("{");
			indent();
			Value::Members::iterator it = members.begin();
			while (true)
			{
				const std::string &name = *it;
				const Value &childValue = value[name];
				writeCommentBeforeValue(childValue);
				writeWithIndent(valueToQuotedString(name.c_str()));
				*document_ << " : ";
				writeValue(childValue);
				if (++it == members.end())
				{
					writeCommentAfterValueOnSameLine(childValue);
					break;
				}
				*document_ << ",";
				writeCommentAfterValueOnSameLine(childValue);
			}
			unindent();
			writeWithIndent("}");
		}
	}
		break;
	}
}

void StyledStreamWriter::writeArrayValue(const Value &value)
{
	unsigned size = value.size();
	if (size == 0)
		pushValue("[]");
	else
	{
		bool isArrayMultiLine = isMultineArray(value);
		if (isArrayMultiLine)
		{
			writeWithIndent("[");
			indent();
			bool hasChildValue = !childValues_.empty();
			unsigned index = 0;
			while (true)
			{
				const Value &childValue = value[index];
				writeCommentBeforeValue(childValue);
				if (hasChildValue)
					writeWithIndent(childValues_[index]);
				else
				{
					writeIndent();
					writeValue(childValue);
				}
				if (++index == size)
				{
					writeCommentAfterValueOnSameLine(childValue);
					break;
				}
				*document_ << ",";
				writeCommentAfterValueOnSameLine(childValue);
			}
			unindent();
			writeWithIndent("]");
		}
		else // output on a single line
		{
			assert( childValues_.size() == size );
			*document_ << "[ ";
			for (unsigned index = 0; index < size; ++index)
			{
				if (index > 0)
					*document_ << ", ";
				*document_ << childValues_[index];
			}
			*document_ << " ]";
		}
	}
}

bool StyledStreamWriter::isMultineArray(const Value &value)
{
	int size = value.size();
	bool isMultiLine = size * 3 >= rightMargin_;
	childValues_.clear();
	for (int index = 0; index < size && !isMultiLine; ++index)
	{
		const Value &childValue = value[index];
		isMultiLine = isMultiLine || ((childValue.isArray()
				|| childValue.isObject()) && childValue.size() > 0);
	}
	if (!isMultiLine) // check if line length > max line length
	{
		childValues_.reserve(size);
		addChildValues_ = true;
		int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
		for (int index = 0; index < size && !isMultiLine; ++index)
		{
			writeValue(value[index]);
			lineLength += int(childValues_[index].length());
			isMultiLine = isMultiLine && hasCommentForValue(value[index]);
		}
		addChildValues_ = false;
		isMultiLine = isMultiLine || lineLength >= rightMargin_;
	}
	return isMultiLine;
}

void StyledStreamWriter::pushValue(const std::string &value)
{
	if (addChildValues_)
		childValues_.push_back(value);
	else
		*document_ << value;
}

void StyledStreamWriter::writeIndent()
{
	/*
	 Some comments in this method would have been nice. ;-)

	 if ( !document_.empty() )
	 {
	 char last = document_[document_.length()-1];
	 if ( last == ' ' )     // already indented
	 return;
	 if ( last != '\n' )    // Comments may add new-line
	 *document_ << '\n';
	 }
	 */
	*document_ << '\n' << indentString_;
}

void StyledStreamWriter::writeWithIndent(const std::string &value)
{
	writeIndent();
	*document_ << value;
}

void StyledStreamWriter::indent()
{
	indentString_ += indentation_;
}

void StyledStreamWriter::unindent()
{
	assert( indentString_.size() >= indentation_.size() );
	indentString_.resize(indentString_.size() - indentation_.size());
}

void StyledStreamWriter::writeCommentBeforeValue(const Value &root)
{
	if (!root.hasComment(commentBefore))
		return;
	*document_ << normalizeEOL(root.getComment(commentBefore));
	*document_ << "\n";
}

void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value &root)
{
	if (root.hasComment(commentAfterOnSameLine))
		*document_ << " " + normalizeEOL(
				root.getComment(commentAfterOnSameLine));

	if (root.hasComment(commentAfter))
	{
		*document_ << "\n";
		*document_ << normalizeEOL(root.getComment(commentAfter));
		*document_ << "\n";
	}
}

bool StyledStreamWriter::hasCommentForValue(const Value &value)
{
	return value.hasComment(commentBefore) || value.hasComment(
			commentAfterOnSameLine) || value.hasComment(commentAfter);
}

std::string StyledStreamWriter::normalizeEOL(const std::string &text)
{
	std::string normalized;
	normalized.reserve(text.length());
	const char *begin = text.c_str();
	const char *end = begin + text.length();
	const char *current = begin;
	while (current != end)
	{
		char c = *current++;
		if (c == '\r') // mac or dos EOL
		{
			if (*current == '\n') // convert dos EOL
				++current;
			normalized += '\n';
		}
		else
			// handle unix EOL & other char
			normalized += c;
	}
	return normalized;
}

std::ostream& operator<<(std::ostream &sout, const Value &root)
{
	Json::StyledStreamWriter writer;
	writer.write(sout, root);
	return sout;
}

} // namespace Json
